package com.changanw.middleware.dbrouter;

import com.changanw.middleware.dbrouter.annotation.DBRouter;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author changanw
 * @description 数据库路由计算  核心计算要分配到哪个库的哪个表上
 * dbRouterConfig，是注入的数据库路由配置信息
 * aopPoint，是定义的切点，也就是注解用在 DAO 后，会被切面拦截
 * doRouter，就是整个切面拦截后的具体操作，也就是数据库路由的计算和设置
 *
 * @date 2024/5/10 13:37
 */
@Aspect
@Component("db-router-point")
public class DBRouterJoinPoint {
  private Logger logger = LoggerFactory.getLogger(DBRouterJoinPoint.class);

  @Autowired
  private DBRouterConfig dbRouterConfig;

  @Pointcut("@annotation(com.changanw.middleware.dbrouter.annotation.DBRouter)")
  public void aopPoint() {
  }

  @Around("aopPoint() && @annotation(dbRouter)")
  public Object doRouter(ProceedingJoinPoint jp, DBRouter dbRouter) throws Throwable {
    String dbKey = dbRouter.key();
    if (StringUtils.isBlank(dbKey)) throw new RuntimeException("annotation DBRouter key is null！");
    // 计算路由
    String dbKeyAttr = getAttrValue(dbKey, jp.getArgs());
    int size = dbRouterConfig.getDbCount() * dbRouterConfig.getTbCount();
    // 扰动函数
    int idx = (size - 1) & (dbKeyAttr.hashCode() ^ (dbKeyAttr.hashCode() >>> 16));
    // 库表索引
    int dbIdx = idx / dbRouterConfig.getTbCount() + 1;
    int tbIdx = idx - dbRouterConfig.getTbCount() * (dbIdx - 1);
    // 设置到 ThreadLocal
    DBContextHolder.setDBKey(String.format("%02d", dbIdx));
    DBContextHolder.setTBKey(String.format("%02d", tbIdx));
    logger.info("数据库路由 method：{} dbIdx：{} tbIdx：{}", getMethod(jp).getName(), dbIdx, tbIdx);
    // 返回结果
    try {
      return jp.proceed();
    } finally {
      DBContextHolder.clearDBKey();
      DBContextHolder.clearTBKey();
    }
  }

  private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
    Signature sig = jp.getSignature();
    MethodSignature methodSignature = (MethodSignature) sig;
    return jp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
  }

  public String getAttrValue(String attr, Object[] args) {
    String filedValue = null;
    for (Object arg : args) {
      try {
        if (StringUtils.isNotBlank(filedValue)) break;
        filedValue = BeanUtils.getProperty(arg, attr);
      } catch (Exception e) {
        logger.error("获取路由属性值失败 attr：{}", attr, e);
      }
    }
    return filedValue;
  }
}
